{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install -q pyjwt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests, jwt\n",
    "from IPython.display import JSON\n",
    "\n",
    "CATALOG_URL = \"http://lakekeeper:8181/catalog\"\n",
    "MANAGEMENT_URL = \"http://lakekeeper:8181/management\"\n",
    "KEYCLOAK_TOKEN_URL = \"http://keycloak:8080/realms/iceberg/protocol/openid-connect/token\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Bootstraping Lakekeeper\n",
    "Initially, Lakekeeper needs to be bootstrapped.\n",
    "During bootstrapping the initial `admin` is set. Bootstrapping can only be performed once. The first user calling the bootstrap endpoint, will become the `admin`.\n",
    "\n",
    "This Notebook performs bootstrapping via python requests. It only works if the server hasn't previously bootstrapped using the UI!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Sign in\n",
    "First, we need to obtain a token from our Identity Provider. In this example a `Keycloak` is running as a pod beside Lakekeeper. A few users have been pre-created in Keycloak for this example. We are now logging into Keycloak as the technical user (client) `spark`. If a human user bootstraps the catalog, we recommend to use the UI.\n",
    "\n",
    "Keycloak can be accessed at http://localhost:30080 in this example. Use `admin` as username and password. Then select the `iceberg` realm on the top left corner."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Login to Keycloak\n",
    "CLIENT_ID = \"spark\"\n",
    "CLIENT_SECRET = \"2OR3eRvYfSZzzZ16MlPd95jhLnOaLM52\"\n",
    "\n",
    "response = requests.post(\n",
    "    url=KEYCLOAK_TOKEN_URL,\n",
    "    data={\n",
    "        \"grant_type\": \"client_credentials\",\n",
    "        \"client_id\": CLIENT_ID,\n",
    "        \"client_secret\": CLIENT_SECRET,\n",
    "        \"scope\": \"lakekeeper\"\n",
    "    },\n",
    "    headers={\"Content-type\": \"application/x-www-form-urlencoded\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "access_token = response.json()['access_token']\n",
    "\n",
    "# Lets inspect the token we got to see that our application name is available:\n",
    "JSON(jwt.decode(access_token, options={\"verify_signature\": False}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we have the access token, we can query the server info Endpoint. \n",
    "On first launch it will show bootstrapped `'bootstrapped': false`.\n",
    "The full API documentation is available as part of the Repository and hosted by Lakekeeper: http://localhost:8181/swagger-ui/#/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "response = requests.get(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/info\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "JSON(response.json())\n",
    "# On first launch it shows \"bootstrapped\": False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Bootstrap"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "response = requests.post(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/bootstrap\",\n",
    "    headers={\n",
    "        \"Authorization\": f\"Bearer {access_token}\"\n",
    "    },\n",
    "    json={\n",
    "        \"accept-terms-of-use\": True,\n",
    "        # Optionally, we can override the name / type of the user:\n",
    "        # \"user-email\": \"user@example.com\",\n",
    "        # \"user-name\": \"Roald Amundsen\",\n",
    "        # \"user-type\": \"human\"\n",
    "    },\n",
    ")\n",
    "response.raise_for_status()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Grant access to UI User\n",
    "In keycloak the user \"Peter\" exists which we are now also assigning the \"admin\" role.\n",
    "\n",
    "Before executing the next cell, login to the UI at http://localhost:8181 using:\n",
    "* Username: `peter`\n",
    "* Password: `iceberg`\n",
    "\n",
    "You should see \"You don't have any projects assignments\".\n",
    "\n",
    "Lets assign permissions to peter:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Users will show up in the /v1/user endpoint after the first login via the UI\n",
    "# or the first call to the /catalog/v1/config endpoint.\n",
    "response = requests.get(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/user\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "JSON(response.json())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "response = requests.post(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/permissions/server/assignments\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    "    json={\n",
    "      \"writes\": [\n",
    "        {\n",
    "          \"type\": \"admin\",\n",
    "          \"user\": \"oidc~cfb55bf6-fcbb-4a1e-bfec-30c6649b52f8\"\n",
    "        }\n",
    "      ]\n",
    "    }\n",
    ")\n",
    "response.raise_for_status()\n",
    "\n",
    "response = requests.post(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/permissions/project/assignments\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    "    json={\n",
    "      \"writes\": [\n",
    "        {\n",
    "          \"type\": \"project_admin\",\n",
    "          \"user\": \"oidc~cfb55bf6-fcbb-4a1e-bfec-30c6649b52f8\"\n",
    "        }\n",
    "      ]\n",
    "    }\n",
    ")\n",
    "response.raise_for_status()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can now refresh the UI page and should see the default Lakehouse."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1 Grant Access to trino & duckdb & starrocks User"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# First we login as the trino user, so that the user is known to\n",
    "# Lakekeeper.\n",
    "\n",
    "for client_id, client_secret in [(\"trino\", \"AK48QgaKsqdEpP9PomRJw7l2T7qWGHdZ\"), (\"duckdb\", \"r2dHUlb7XrkSRcvrRqG5XZwQfnUS5NlL\"), (\"starrocks\", \"X5IWbfDJBTcU1F3PGZWgxDJwLyuFQmSf\")]:\n",
    "    response = requests.post(\n",
    "        url=KEYCLOAK_TOKEN_URL,\n",
    "        data={\n",
    "            \"grant_type\": \"client_credentials\",\n",
    "            \"client_id\": client_id,\n",
    "            \"client_secret\": client_secret,\n",
    "            \"scope\": \"lakekeeper\"\n",
    "        },\n",
    "        headers={\"Content-type\": \"application/x-www-form-urlencoded\"},\n",
    "    )\n",
    "    response.raise_for_status()\n",
    "    access_token_client = response.json()['access_token']\n",
    "    \n",
    "    response = requests.post(\n",
    "        url=f\"{MANAGEMENT_URL}/v1/user\",\n",
    "        headers={\"Authorization\": f\"Bearer {access_token_client}\"},\n",
    "        json={\"update-if-exists\": True}\n",
    "    )\n",
    "    response.raise_for_status()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Users will show up in the /v1/user endpoint after the first login via the UI\n",
    "# or the first call to the /catalog/v1/config endpoint.\n",
    "response = requests.get(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/user\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "JSON(response.json())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "response = requests.post(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/permissions/project/assignments\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    "    json={\n",
    "      \"writes\": [\n",
    "        {\n",
    "            \"type\": \"project_admin\",\n",
    "            \"user\": \"oidc~94eb1d88-7854-43a0-b517-a75f92c533a5\"\n",
    "        },\n",
    "        {\n",
    "            \"type\": \"project_admin\",\n",
    "            \"user\": \"oidc~7515be4b-ce5b-4371-ab31-f40b97f74ec6\"\n",
    "        },\n",
    "        {\n",
    "            \"type\": \"project_admin\",\n",
    "            \"user\": \"oidc~7a5da0c5-24e2-4148-a8d9-71c748275928\"\n",
    "        }\n",
    "      ]\n",
    "    }\n",
    ")\n",
    "response.raise_for_status()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Validate Bootstrap"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The server is now bootstrapped:\n",
    "response = requests.get(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/info\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "JSON(response.json())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# An initial user was created\n",
    "response = requests.get(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/user\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "JSON(response.json())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The \"admin\" role has been assigned:\n",
    "response = requests.get(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/permissions/server/assignments\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "user_id = response.json()['assignments'][0]['user']\n",
    "JSON(response.json())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This user is the global admin, which has all access rights to the server:\n",
    "response = requests.get(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/server/actions\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "JSON(response.json())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Lets see who this user is:\n",
    "response = requests.get(\n",
    "    url=f\"{MANAGEMENT_URL}/v1/user/{user_id}\",\n",
    "    headers={\"Authorization\": f\"Bearer {access_token}\"},\n",
    ")\n",
    "response.raise_for_status()\n",
    "JSON(response.json())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
